
#include <type_traits>

#include "hk32f030m.h"

// 用来生成引脚定义的宏，展开形式类似如下：
//   constexpr PinToken PA0 = {GPIOA_BASE, RCC_AHBPeriph_GPIOA, GPIO_Pin_0, GPIO_PinSource0}
#define _GPIO_DEF_PIN_TOKEN(PORT, PIN) \
    constexpr PinToken P##PORT##PIN = {GPIO##PORT##_BASE, RCC_AHBPeriph_GPIO##PORT, GPIO_Pin_##PIN, GPIO_PinSource##PIN}

#define _GPIO_PORT_TO_POINTER(port) reinterpret_cast<decltype(GPIOA)>(port)

#define _GPIO_ENUM_TO_UNDERLYING(e) static_cast<std::underlying_type_t<decltype(e)>>(e)

#define _GPIO_ENUM_TO_ENUM(e1, e2) static_cast<e2>(static_cast<std::underlying_type_t<decltype(e1)>>(e1))


/**
 * @brief 定义了HK32F030M 和0301M 所有GPIO 引脚，特定封装下可能不是全部可用
 *
 * 在F030M 下：
 * PA0 是NRST 引脚的复用功能，F030M 上可能无法启用；PD7 就是VCAP 引脚，可以用作GPIO，但不一定能使用复用功能。
 *
 * 在F0301M 下：
 * 包括PA0 和PD7 在内，共18 个IO 可用。复用NRST 引脚后，编程时只能使用上电复位。
 */
namespace gpio {

    // PortType 不能定义为GPIOA 的类型，因为GPIOA 是从GPIOA_BASE 强制转换的一个指针，
    // 这种转换和reinterpret_cast 是一样的，不能用在constexpr 的初始化过程中，
    // 后面要用GPIOA 指针的地方再手动转换一下。
    using PortType = decltype(GPIOA_BASE);
    using PortClkEnableType = decltype(RCC_AHBPeriph_GPIOA);
    using PinType = decltype(GPIO_Pin_0);
    using PinSourceType = decltype(GPIO_PinSource0);


    struct PinToken {
        PortType port;
        PortClkEnableType port_clk_en;
        PinType pin;
        PinSourceType pin_source;
    };


    // PAx
    _GPIO_DEF_PIN_TOKEN(A, 0);  // NRST 复用功能
    _GPIO_DEF_PIN_TOKEN(A, 1);
    _GPIO_DEF_PIN_TOKEN(A, 2);
    _GPIO_DEF_PIN_TOKEN(A, 3);

    // PBx
    _GPIO_DEF_PIN_TOKEN(B, 4);
    _GPIO_DEF_PIN_TOKEN(B, 5);

    // PCx
    _GPIO_DEF_PIN_TOKEN(C, 3);
    _GPIO_DEF_PIN_TOKEN(C, 4);
    _GPIO_DEF_PIN_TOKEN(C, 5);
    _GPIO_DEF_PIN_TOKEN(C, 6);
    _GPIO_DEF_PIN_TOKEN(C, 7);

    // PDx
    _GPIO_DEF_PIN_TOKEN(D, 1);
    _GPIO_DEF_PIN_TOKEN(D, 2);
    _GPIO_DEF_PIN_TOKEN(D, 3);
    _GPIO_DEF_PIN_TOKEN(D, 4);
    _GPIO_DEF_PIN_TOKEN(D, 5);
    _GPIO_DEF_PIN_TOKEN(D, 6);
    _GPIO_DEF_PIN_TOKEN(D, 7);  // VCAP 引脚


    constexpr PortClkEnableType _calc_port_clk_sum(PinToken pin) {
        return pin.port_clk_en;
    }

    template <typename... Ts>
    constexpr PortClkEnableType _calc_port_clk_sum(PinToken pin, Ts... args) {
        return pin.port_clk_en | _calc_port_clk_sum(args...);
    }

    template <typename... Ts>
    //__attribute__((always_inline)) inline static void enable_clk(const PinToken pin, const Ts... args) {
    inline static void enable_clk(PinToken pin, Ts... args) {
        // RCC_AHBPeriphClockCmd(_calc_port_clk_sum(pin, args...), ENABLE);
        RCC->AHBENR |= _calc_port_clk_sum(pin, args...);
    }

    template <typename... Ts>
    inline static void disable_clk(PinToken pin, Ts... args) {
        // RCC_AHBPeriphClockCmd(_calc_port_clk_sum(pin, args...), DISABLE);
        RCC->AHBENR &= ~_calc_port_clk_sum(pin, args...);
    }


    // 高8 字节表示输出类型，低8 字节为模式
    enum class pin_mode {
        in = GPIO_Mode_IN,
        out_pp = GPIO_Mode_OUT | (GPIO_OType_PP << 8),
        out_od = GPIO_Mode_OUT | (GPIO_OType_OD << 8),
        af_pp = GPIO_Mode_AF | (GPIO_OType_PP << 8),  // AF 模式不需要手动设置，对应的外设函数会自动配置
        af_od = GPIO_Mode_AF | (GPIO_OType_OD << 8),
        an = GPIO_Mode_AN,
    };

    enum class pull_mode {
        no = GPIO_PuPd_NOPULL,
        up = GPIO_PuPd_UP,
        down = GPIO_PuPd_DOWN
    };

    enum class speed {
        low = GPIO_Speed_2MHz,
        high = GPIO_Speed_10MHz,
        // high = GPIO_Speed_50MHz  不支持50Mhz
    };

    enum class schmit {
        disable = GPIO_Schmit_Disable,
        enable = GPIO_Schmit_Enable,
    };

    // void
    // init_pin(PinToken pin, pin_mode mode, pull_mode pull = pull_mode::up, speed sp = speed::high, schmit sh = schmit::enable) {
    //     GPIO_InitTypeDef init_struct = {.GPIO_Pin = pin.pin,
    //                                     .GPIO_Mode = static_cast<GPIOMode_TypeDef>(_GPIO_ENUM_TO_UNDERLYING(mode) & 0x00ff),
    //                                     .GPIO_Speed = _GPIO_ENUM_TO_ENUM(sp, GPIOSpeed_TypeDef),
    //                                     .GPIO_OType = static_cast<GPIOOType_TypeDef>(_GPIO_ENUM_TO_UNDERLYING(mode) >> 8),
    //                                     .GPIO_PuPd = _GPIO_ENUM_TO_ENUM(pull, GPIOPuPd_TypeDef),
    //                                     .GPIO_Schmit = _GPIO_ENUM_TO_ENUM(sh, GPIOSchmit_TypeDef)};
    //     GPIO_Init(_GPIO_PORT_TO_POINTER(pin.port), &init_struct);
    // }


    constexpr GPIO_InitTypeDef make_empty_init() {
        return GPIO_InitTypeDef{};
    }


    /**
     * @brief 按照输入参数初始化空的GPIO_TypeDef 结构体，然后初始化GPIO，传入的GPIO_TypeDef 可以被void init_pin(PinToken pin, GPIO_TypeDef &init_struct)复用。
     *
     * @param pin 指向GPIO 引脚的PinToken，应该使用定义好的PAx 等常量
     * @param init_struct 初始化结构体的所有数据将被覆盖
     * @param mode 配置输入、输出、复用、模拟输入，以及推挽或开漏输出
     * @param pull 配置上拉、下拉或浮空
     * @param sp 配置速度
     * @param sh 配置输入施密特触发器
     */
    void init_pin(PinToken pin, GPIO_InitTypeDef& init_struct, pin_mode mode, pull_mode pull, speed sp, schmit sh) {
        init_struct.GPIO_Pin = pin.pin;
        init_struct.GPIO_Mode = static_cast<GPIOMode_TypeDef>(_GPIO_ENUM_TO_UNDERLYING(mode) & 0x00ff);
        init_struct.GPIO_Speed = _GPIO_ENUM_TO_ENUM(sp, GPIOSpeed_TypeDef);
        init_struct.GPIO_OType = static_cast<GPIOOType_TypeDef>(_GPIO_ENUM_TO_UNDERLYING(mode) >> 8);
        init_struct.GPIO_PuPd = _GPIO_ENUM_TO_ENUM(pull, GPIOPuPd_TypeDef);
        init_struct.GPIO_Schmit = _GPIO_ENUM_TO_ENUM(sh, GPIOSchmit_TypeDef);
        GPIO_Init(_GPIO_PORT_TO_POINTER(pin.port), &init_struct);
    }


    /**
     * @brief 复用已经配置好的GPIO_TypeDef 结构体继续配置引脚
     *
     * 使用方法是先用带配置参数的init_pin 重载把初始化结构体配置好，然后复用该结构体，调用这个函数配置其他参数相同的引脚
     *
     * @param pin 指向GPIO 引脚的PinToken，应该使用定义好的PAx 等常量
     * @param init_struct 只修改初始化结构体的GPIO_Pin，其他保持原样
     */
    void init_pin(PinToken pin, GPIO_InitTypeDef& init_struct) {
        init_struct.GPIO_Pin = pin.pin;
        GPIO_Init(_GPIO_PORT_TO_POINTER(pin.port), &init_struct);
    }


    /**
     * @brief 内部创建一个GPIO_TypeDef 结构体，返回后丢弃
     *
     * @param pin 指向GPIO 引脚的PinToken，应该使用定义好的PAx 等常量
     * @param init_struct 初始化结构体的所有数据将被覆盖
     * @param mode 配置输入、输出、复用、模拟输入，以及推挽或开漏输出
     * @param pull 配置上拉、下拉或浮空，默认为上拉
     * @param sp 配置速度，默认为中速/10MHz
     * @param sh 配置输入施密特触发器，默认使能
     */
    void init_pin(PinToken pin, pin_mode mode, pull_mode pull = pull_mode::up, speed sp = speed::high, schmit sh = schmit::enable) {
        auto init_struct = make_empty_init();
        init_pin(pin, init_struct, mode, pull, sp, sh);
    }


    inline static void set_pin_mode(PinToken pin, pin_mode mode) {
        _GPIO_PORT_TO_POINTER(pin.port)->MODER &= ~(GPIO_MODER_MODER0 << (pin.pin_source * 2));
        _GPIO_PORT_TO_POINTER(pin.port)->MODER |= ((_GPIO_ENUM_TO_UNDERLYING(mode) & 0x00ff) << (pin.pin_source * 2));
        if (mode == pin_mode::out_od || mode == pin_mode::out_pp || mode == pin_mode::af_od || mode == pin_mode::af_pp) {
            _GPIO_PORT_TO_POINTER(pin.port)->OTYPER &= ~((GPIO_OTYPER_OT_0) << pin.pin_source);
            _GPIO_PORT_TO_POINTER(pin.port)->OTYPER |= static_cast<uint16_t>((_GPIO_ENUM_TO_UNDERLYING(mode) >> 8) << (pin.pin_source));
        }
    }


    inline static auto test_pin(PinToken pin) {
        return _GPIO_PORT_TO_POINTER(pin.port)->IDR & pin.pin;
    }

    // TODO: 批量操作

    inline static void setpin(PinToken pin) {
        _GPIO_PORT_TO_POINTER(pin.port)->BSRR = pin.pin;
    }


    inline static void clrpin(PinToken pin) {
        _GPIO_PORT_TO_POINTER(pin.port)->BRR = pin.pin;
    }


    inline static void write_pin(PinToken pin, uint8_t level) {
        if (level == 0) {
            clrpin(pin);
        }
        else {
            setpin(pin);
        }
    }


    inline static void toggle_pin(PinToken pin) {
        _GPIO_PORT_TO_POINTER(pin.port)->ODR ^= pin.pin;
    }

    // TODO: AF config


    class ChainInit {
       private:
        GPIO_InitTypeDef init_struct;

       public:
        /**
         * @brief 修改初始化结构体并配置引脚，返回值可以链式调用继续初始化下一个引脚
         *
         * @param pin 指向GPIO 引脚的PinToken，应该使用定义好的PAx 等常量
         * @param init_struct 初始化结构体的所有数据将被覆盖
         * @param mode 配置输入、输出、复用、模拟输入，以及推挽或开漏输出
         * @param pull 配置上拉、下拉或浮空，默认为上拉
         * @param sp 配置速度，默认为高速/10MHz
         * @param sh 配置输入施密特触发器，默认使能
         * @return ChainInit&
         */
        inline ChainInit& init(PinToken pin, pin_mode mode, pull_mode pull = pull_mode::up, speed sp = speed::high, schmit sh = schmit::enable) {
            gpio::init_pin(pin, this->init_struct, mode, pull, sp, sh);
            return *this;
        }


        /**
         * @brief 复用已经配置好的GPIO_TypeDef 结构体继续配置引脚
         *
         * @param pin 指向GPIO 引脚的PinToken，应该使用定义好的PAx 等常量
         * @return ChainInit&
         */
        inline ChainInit& init(PinToken pin) {
            gpio::init_pin(pin, this->init_struct);
            return *this;
        }
    };

}  // namespace gpio


void nrst_pin_switch_as_pa0() {
    // 将NRST 复用为PA0
    RCC->APB1ENR |= RCC_APB1ENR_IOMUXEN;
    GPIOMUX->NRST_PIN_KEY = 0x5AE1;
    GPIOMUX->NRST_PA0_SEL = 1;
}


void Delay(uint32_t nCount)  // 简单的延时函数
{
    for (; nCount != 0; nCount--)
        __NOP();
}


constexpr auto LED0 = gpio::PA0;
constexpr auto LED1 = gpio::PC7;
constexpr auto LED2 = gpio::PD7;

int main(void) {
    // 测试PA0 和PD7 能不能用
    nrst_pin_switch_as_pa0();

    // LED 端口初始化
    gpio::enable_clk(LED0, LED1, LED2);

    // 设置三个LED 引脚为推挽输出
    gpio::ChainInit()
        .init(LED0, gpio::pin_mode::out_pp)
        .init(LED1)
        .init(LED2);

    using namespace gpio;

    while (1) {
        clrpin(LED0);  // 亮
        Delay(0x0FFFFF);
        setpin(LED0);  // 灭

        clrpin(LED1);  // 亮
        Delay(0x0FFFFF);
        setpin(LED1);  // 灭

        clrpin(LED2);  // 亮
        Delay(0x0FFFFF);
        setpin(LED2);  // 灭
    }
}
